import { defineGlobalProperty, isMyRoom } from '@/ayaka'
import { goTo } from '../move'
import StructureCache from '../structureCache'
import BattleCache from './cache'
import RoomArray from '@/utils/roomArray'
import BattleCalc from './calc'
import { getOppositeDirection, isNearEdge, onEdge, serializePos } from '@/utils/position'
import { creepPosBipartiteMatch } from '@/algorithm/bipartiteMatch'
import FlagController, { FlagPrefix } from '../flag'
import BattleVisual from './visual'

// 不能修改它
const emptyCostMatrix = new PathFinder.CostMatrix()

// 临时房间数组，调用`.fill(0)`重置
const tempRoomArray = new RoomArray()

/**
 * 战斗移动相关
 */
export default class BattleMove {
    /**
     * 小队路径缓存
     */
    public static cacheTeamPath: { [flagName: string]: RoomPosition[] } = {}

    /**
     * 缓存不可见房间的 CostMatrix
     */
    public static cacheCostMatrix: { [flagName: string]: CostMatrix } = {}

    /**
     * 位置掩码映射
     */
    public static POS_MARK_MAP = {
        ['↖']: [0, 0],
        ['↗']: [1, 0],
        ['↙']: [0, 1],
        ['↘']: [1, 1],
    }

    /**
     * 低于多少血量的建筑视为可以通行
     */
    public static structHitLimit = 10e3

    /**
     * 小队集合，尽量将小队内的 creep 集合到一起
     */
    public static gatherCreeps(flag: Flag) {
        if (flag.memory.isGathered) return true

        const creeps = flag._creeps
        if (creeps.length <= 1) {
            // 只有一个爬不需要集合
            return (flag.memory.isGathered = true)
        }

        // 看看房间内是否有集合旗帜
        const gatherFlag = FlagController.getFlagByPrefixAndRoom(FlagPrefix.teamGather, flag.memory.belong)
        if (gatherFlag) {
            // 所有爬前往集合点
            let isGathered = true
            creeps.forEach((creep) => {
                if (creep.pos.getRangeTo(gatherFlag) <= 1) {
                    creep.memory.stand = true
                    return
                }

                delete creep.memory.stand
                goTo(creep, gatherFlag.pos, { disableRouteCache: true })
                isGathered = false
            })

            return (flag.memory.isGathered = isGathered)
        }

        // 没集合旗帜的话，走到家门口
        const head = creeps[0]
        if (isNearEdge(head.pos, 3)) {
            return (flag.memory.isGathered = true)
        }

        goTo(head, flag.pos, { disableRouteCache: true })
        for (let i = 1; i < creeps.length; i++) {
            const creep = creeps[i]
            const front = creeps[i - 1]
            if (!creep.pos.isNearTo(front)) {
                goTo(creep, front.pos, { disableRouteCache: true })
            }
        }
        return false
    }

    /**
     * 线性移动，保证所有爬挨着，仅用于赶路，近战时勿用
     */
    public static linearMove(flag: Flag, targetPos: RoomPosition = flag.pos, reverse = false) {
        // 线性移动是需要是攻击-奶-攻击-奶，这样方便中途治疗
        let creeps: Creep[] = []
        if (flag._creeps.length === 4) {
            creeps = [flag._creeps[0], flag._creeps[2], flag._creeps[1], flag._creeps[3]]
        } else if (flag._creeps.length === 3) {
            creeps = [flag._creeps[0], flag._creeps[2], flag._creeps[1]]
        } else {
            creeps = flag._creeps
        }

        if (reverse) {
            creeps = [...creeps].reverse()
        }

        const head = creeps[0]

        // 一个爬直接走
        if (creeps.length <= 1) {
            goTo(head, targetPos, { disableRouteCache: true })
            return
        }

        // 要走一起走，要么都不走
        // 有人疲劳不走
        if (creeps.some((creep) => creep.fatigue > 0)) return

        for (let i = 0; i < creeps.length; i++) {
            const creep = creeps[i]
            const back = creeps[i + 1] || creep

            // 后面爬没跟上不走
            if (!creep.pos.isCrossRoomNearTo(back.pos)) {
                continue
            }

            if (i === 0) {
                // TODO: 这个地方需要考虑一下禁用对穿是否会导致小队其他爬挡住了路
                goTo(creep, targetPos, { disableCross: true, preferHighway: flag.memory.preferHighway })
            } else if (creep.pos.isCrossRoomNearTo(creeps[i - 1].pos)) {
                creep.move(creep.pos.getDirectionTo(creeps[i - 1].pos))
            } else {
                creep.moveTo(creeps[i - 1])
            }
        }
    }

    /**
     * 获取小队移动方向
     */
    public static getTeamMoveDirection(flag: Flag, goals: (Creep | Structure | Flag)[], pathMode?: 'flee') {
        const creeps = flag._creeps
        const originPos = BattleMove.getOriginPos(flag)!
        const status = pathMode || flag.memory.status
        const needAvoid = status === 'flee' || status === 'avoid'
        const isFlee = status === 'flee'
        const oldMoveMode = flag.memory.moveMode
        const isFourTeam = creeps.length >= 3
        flag.memory.moveMode = status

        // 和上一 tick 的移动模式相同，并且不需要避让，则检查缓存并按缓存移动
        if (
            !needAvoid &&
            originPos.roomName !== flag.pos.roomName &&
            status === oldMoveMode &&
            BattleMove.checkCachePath(flag)
        ) {
            return BattleMove.getDirectionByCachePath(flag)
        } else {
            BattleMove.clearPathCache(flag)
        }

        // 和爬相邻的目标
        const nearGoals = goals.filter((goal) => creeps.find((creep) => creep.pos.isNearTo(goal.pos)))
        if (!needAvoid && nearGoals.length) {
            // 当第一个目标是旗帜，代表没有其他目标
            if (nearGoals[0] instanceof Flag) {
                return BattleMove.getSamllMoveDirection(flag, nearGoals[0].pos)
            }

            // 找血量最少的那个
            const target = (nearGoals as (Creep | Structure)[]).reduce((min, cur) => (min.hits < cur.hits ? min : cur))
            if (isFourTeam) {
                return BattleMove.getSamllMoveDirection(flag, target.pos)
            } else if (target instanceof Creep) {
                return originPos.getDirectionTo(target.pos)
            }
        }

        const allGoals: { pos: RoomPosition; range: number }[] = []
        // 修改范围，逃跑模式下避开有攻击力的爬
        goals.forEach((goal) => {
            // 逃跑状态只避开爬和塔
            if (isFlee && !(goal instanceof Creep) && !(goal instanceof StructureTower)) return

            goal._range =
                !isFlee || !(goal instanceof Creep)
                    ? 1
                    : goal.getActiveBodyparts(RANGED_ATTACK)
                    ? 5
                    : goal.getActiveBodyparts(ATTACK)
                    ? 3
                    : 1

            // 调整 spawn 的范围，避免被踩死
            if (goal instanceof StructureSpawn) {
                goal._range = 2
            }
            allGoals.push({ pos: goal.pos, range: goal._range })
        })

        if (status === 'flee') {
            allGoals.push(...(flag._avoidObjs || []))
        }

        const newGoals: { pos: RoomPosition; range: number }[] = []
        const visited = new Set<number>()
        const rangePlus = status === 'flee' ? 2 : status === 'avoid' ? 1 : 0

        allGoals.sort((a, b) => b.range - a.range)

        // 一体机或者二人小队模式
        if (!isFourTeam) {
            allGoals.forEach((goal) => {
                // 不考虑在房间边缘的目标
                if (onEdge(goal.pos)) {
                    return
                }

                if (visited.has(goal.pos.hashCode())) return

                visited.add(goal.pos.hashCode())
                newGoals.push({ pos: goal.pos, range: goal.range + rangePlus })
            })
        } else {
            // 四人小队模式
            allGoals.forEach((goal) => {
                // 不考虑在房间边缘的目标
                if (onEdge(goal.pos)) {
                    return
                }

                for (const item of [
                    [0, 0],
                    [-1, 0],
                    [0, -1],
                    [-1, -1],
                ]) {
                    const pos = new RoomPosition(goal.pos.x + item[0], goal.pos.y + item[1], goal.pos.roomName)
                    if (visited.has(pos.hashCode())) return

                    visited.add(pos.hashCode())
                    newGoals.push({ pos, range: goal.range + rangePlus })
                }
            })
        }

        // 调试
        const costMatrixFlag = Game.flags[`costMatrixShow`]

        const result = PathFinder.search(originPos, newGoals, {
            roomCallback(roomName) {
                if (Memory.bypassRooms && Memory.bypassRooms.includes(roomName)) return false

                const isTargetRoom = flag.pos.roomName === roomName
                if (isTargetRoom && originPos.roomName !== flag.pos.roomName && BattleMove.cacheCostMatrix[flag.name]) {
                    return BattleMove.cacheCostMatrix[flag.name]
                }

                const costs = BattleMove.getMoveAbleCostMatrix(
                    roomName,
                    creeps,
                    flag._avoidObjs,
                    flag._max_damage,
                    rangePlus,
                    creeps.length >= 3,
                    needAvoid ? 50 : 5,
                    flag.memory.structHitLimit || BattleMove.structHitLimit,
                    flag.memory.isSpawnDanger,
                )

                if (isTargetRoom) {
                    BattleMove.cacheCostMatrix[flag.name] = costs
                }

                if (costMatrixFlag && costMatrixFlag.pos.roomName === roomName && costMatrixFlag.color === COLOR_RED) {
                    BattleVisual.drawRoomArray(roomName, costs)
                }

                return costs
            },
            flee: status === 'flee',
            maxRooms: originPos.roomName === flag.pos.roomName ? 2 : 20,
            maxOps: 8000,
        })

        // 没找到路径或者路径堵塞了
        if (!result.path.length || BattleMove.hasObstacleInPath(creeps, result.path[0])) {
            // 无其他目标
            if (goals[0] instanceof Flag) {
                // 休眠
                flag.memory.status = 'sleep'
            }
            return
        }

        BattleMove.cacheTeamPath[flag.name] = result.path
        return originPos.getDirectionTo(result.path.shift()!)
    }

    /**
     * 小队移动
     */
    public static teamMove(flag: Flag) {
        if (BattleMove.hasCreepFatigue(flag._creeps)) return

        // 目标房间的出口
        const exits = Game.map.describeExits(flag.pos.roomName)
        // 是否在出口房间
        const isInExits = [...Object.values(exits), flag.pos.roomName].includes(flag._creeps[0].room.name)
        const isSquare = BattleMove.isSquare(flag._creeps)
        const hasOnEdge = BattleMove.hasCreepOnEdge(flag._creeps)
        flag._hasOnEdge = hasOnEdge

        if (isSquare && isInExits) {
            flag.memory.formation = 'square'
        }

        if (
            flag._creeps.length >= 3 &&
            !isSquare &&
            !hasOnEdge &&
            (isInExits || flag.memory.formation === 'square') &&
            flag.memory.formSquareTime !== Game.time - 1
        ) {
            // 四人小队尝试组队
            if (BattleMove.formSquare(flag)) {
                flag.memory.formSquareTime = Game.time
                return
            }
        }

        if (flag.memory.formation === 'square' && isSquare) {
            flag._targets = flag._targets || [flag]
            // 在边缘就不尝试交换位置，否则交换，失败后直接走
            if (hasOnEdge || !BattleMove.switchTeam4Pos(flag)) {
                let direction = BattleMove.getTeamMoveDirection(flag, flag._targets)
                if (!direction && flag.memory.status === 'avoid') {
                    direction = BattleMove.getTeamMoveDirection(flag, flag._targets, 'flee')
                }

                BattleMove.moveByDirection(flag, direction)
            }
        } else if (flag.memory.formation === 'square' && flag._creeps.length >= 2 && onEdge(flag._creeps[0].pos)) {
            BattleMove.linearMove(flag, flag._creeps[flag._creeps.length - 1].pos, true)
        } else {
            BattleMove.linearMove(flag)
        }
    }

    /**
     * 按方向移动
     */
    public static moveByDirection(flag: Flag, direction?: DirectionConstant) {
        const creeps = flag._creeps

        // 没有方向
        if (!direction) {
            if (creeps.length !== 2) return

            // 2 人小队看一下第二个爬是否在边界，如果是就移动到周围不是边界的空位
            if (creeps[0].pos.isNearTo(creeps[1]) && onEdge(creeps[1].pos)) {
                BattleMove.moveOuterBorder(creeps[1], creeps[0])
            }
            return
        }

        // 四人小队下一步跨房间不走
        if (creeps.length >= 3) {
            if (
                creeps.find(
                    (creep) => onEdge(creep.pos) && creep.pos.getDirectPos(direction).roomName != creep.pos.roomName,
                )
            ) {
                return
            }
        }

        if (creeps.length >= 3) {
            creeps.forEach((creep) => creep.move(direction))
        } else if (creeps.length === 1) {
            creeps[0].move(direction)
        } else {
            // 二人小队，如果是躲避或者逃跑，反过来移动
            if (flag.memory.status === 'avoid' || flag.memory.status === 'flee') {
                creeps[1].move(direction)
                if (creeps[0]) {
                    creeps[0].move(creeps[0].pos.getDirectionTo(creeps[1]))
                }
            } else {
                creeps[0].move(direction)
                if (creeps[1]) {
                    creeps[1].move(creeps[1].pos.getDirectionTo(creeps[0]))
                }
            }
        }
    }

    /**
     * 根据缓存路径返回方向
     */
    public static getDirectionByCachePath(flag: Flag) {
        const originPos = BattleMove.getOriginPos(flag)!
        const path = BattleMove.cacheTeamPath[flag.name]

        if (!path || !path.length) return undefined

        let nextPos = path[0]
        let direction = originPos.getDirectionTo(nextPos)
        path.shift()
        if (!direction && path.length) {
            // 边界
            nextPos = path[0]
            direction = originPos.getDirectionTo(nextPos)
            path.shift()
        }

        if (BattleMove.hasObstacleInPath(flag._creeps, nextPos)) {
            return undefined
        }

        return direction
    }

    /**
     * 检查缓存路径是否正确
     */
    public static checkCachePath(flag: Flag) {
        const originPos = BattleMove.getOriginPos(flag)

        return !!(
            BattleMove.cacheTeamPath[flag.name] &&
            BattleMove.cacheTeamPath[flag.name].length &&
            originPos?.isCrossRoomNearTo(BattleMove.cacheTeamPath[flag.name][0])
        )
    }

    /**
     * 组成方形阵型，不得少于 3 个爬
     *
     * @returns true 表示进行了队形调整，false 表示没有调整，是否组成了四人队形还需下一 tick 判断
     */
    public static formSquare(flag: Flag) {
        const creeps = flag._creeps

        if (creeps.length <= 2 || BattleMove.isSquare(creeps) !== false) return false

        // 暂时只考虑两种情况，1. 所有爬能连成线；2. 上次是四人队形
        // 对于 1，只需要以中间两个爬为基准，让另两个爬走一步即可。
        // 对于 2，我们只需要记住上次移动的方向以及每个爬是否动了，然后让动了的爬反方向走回来即可
        // 对于其他情况（比较少见），我们先让爬走成一条线，然后用 1 的解法

        if (flag.memory.lastMoveData?.direction) {
            const direction = flag.memory.lastMoveData.direction
            const movedCreeps = creeps.filter((creep) => serializePos(creep.pos) !== creep.memory.lastPosStr)

            // 每个爬都动了说明这种策略错了
            if (movedCreeps.length === creeps.length) return false

            // 反方向走回来
            movedCreeps.forEach((creep) => {
                creep.move(getOppositeDirection(direction))
            })
            return true
        } else if (BattleMove.isLinear(creeps)) {
            return BattleMove.formLinearToSquare(creeps)
        } else {
            BattleMove.linearMove(flag, creeps[0].pos)
            return true
        }
    }

    /**
     * 是否是线性队形
     */
    public static isLinear(creeps: Creep[]) {
        if (creeps.length === 1) return true

        if (creeps.length === 2) {
            return creeps[0].pos.isCrossRoomNearTo(creeps[1].pos)
        }

        // 检测每个爬是否与其余所有爬相邻
        const nearNumbers = creeps.map((creep) => {
            return creeps.filter((other) => creep !== other && creep.pos.isCrossRoomNearTo(other.pos)).length
        })

        return nearNumbers.every((num) => num >= 1) && nearNumbers.some((num) => num === 2)
    }

    /**
     * 线性队形组成四人队形
     */
    public static formLinearToSquare(creeps: Creep[]) {
        // 获取中间两个爬，即：有两个相邻爬的爬
        // o-o-o-o or o-o-o
        const middleCreeps = creeps.filter((creep) => {
            const nearCreeps = creeps.filter((other) => creep !== other && creep.pos.isNearTo(other))
            return nearCreeps.length === 2
        })

        // 3 个爬时可能小于 2，这时候把队首加进去
        if (middleCreeps.length < 2) {
            middleCreeps.push(creeps[0])
        }
        const [middlePos1, middlePos2] = middleCreeps.map((creep) => creep.pos)
        const creepPosHashSet = new Set(creeps.map((creep) => creep.pos.hashCode()))

        // 中间的两个爬只有 6 种姿势，设 o 为爬，- 为空位
        // 1. o-   2. -o   3. oo  4. --  5. o-   6. -o
        //    -o      o-      --     oo     o-      -o
        // 我们要做的就是判断是哪种情况，然后把其他的爬填入空位（使用二分匹配）

        // 生成四个坐标
        const genRoomPositions = (x1: number, y1: number, x2: number, y2: number) => {
            return [
                middlePos1,
                middlePos2,
                new RoomPosition(x1, y1, middlePos1.roomName),
                new RoomPosition(x2, y2, middlePos2.roomName),
            ]
        }

        // 二分匹配
        const bipartiteMatch = (creeps: Creep[], pos: RoomPosition[]) => {
            // 检查后两个位置是否有效
            if (pos.find((p) => !creepPosHashSet.has(p.hashCode()) && !p.walkable(true))) return {}

            return creepPosBipartiteMatch(creeps, pos)
        }

        let result: Record<string, RoomPosition> = {}
        // 情况 5,6
        if (middlePos1.x === middlePos2.x) {
            const minY = Math.min(middlePos1.y, middlePos2.y)

            // 情况 5
            result = bipartiteMatch(creeps, genRoomPositions(middlePos1.x + 1, minY, middlePos1.x + 1, minY + 1))
            if (Object.keys(result).length !== creeps.length) {
                // 情况 6
                result = bipartiteMatch(creeps, genRoomPositions(middlePos1.x - 1, minY, middlePos1.x - 1, minY + 1))
            }
        }
        // 情况 3,4
        else if (middlePos1.y === middlePos2.y) {
            const minX = Math.min(middlePos1.x, middlePos2.x)

            // 情况 3
            result = bipartiteMatch(creeps, genRoomPositions(minX, middlePos1.y + 1, minX + 1, middlePos1.y + 1))
            if (Object.keys(result).length !== creeps.length) {
                // 情况 4
                result = bipartiteMatch(creeps, genRoomPositions(minX, middlePos1.y - 1, minX + 1, middlePos1.y - 1))
            }
        }
        // 情况 1,2
        else {
            const minX = Math.min(middlePos1.x, middlePos2.x)
            const minY = Math.min(middlePos1.y, middlePos2.y)
            const roomName = middlePos1.roomName

            // 情况 3
            result = bipartiteMatch(creeps, [
                new RoomPosition(minX, minY, roomName),
                new RoomPosition(minX, minY + 1, roomName),
                new RoomPosition(minX + 1, minY, roomName),
                new RoomPosition(minX + 1, minY + 1, roomName),
            ])
        }

        if (Object.keys(result).length !== creeps.length) {
            return false
        }

        creeps.forEach((creep) => {
            const targetPos = result[creep.name]
            if (!creep.pos.isEqualTo(targetPos)) {
                creep.move(creep.pos.getDirectionTo(targetPos))
            }
        })
        return true
    }

    /**
     * 相邻移动，小队中只有一个爬与目标相邻时，通过该方法获取下一步的移动方向使得攻击姿态最佳（有两个爬相邻）
     *
     * @return 返回移动的方向
     */
    public static getSamllMoveDirection(flag: Flag, targetPos: RoomPosition) {
        const creeps = flag._creeps
        // 左上角坐标
        const originPos = BattleMove.getOriginPos(flag)!

        // 检查指定方向小队是否可以移动
        const checkTeamMoveAble = (direction: DirectionConstant) => {
            // 移动后的左上角坐标
            const pos = originPos.getDirectPos(direction)
            // 移动后的小队位置
            const nextTeamPos = Object.values(BattleMove.POS_MARK_MAP).map(([x, y]) => {
                return new RoomPosition(pos.x + x, pos.y + y, pos.roomName)
            })
            // 移动后需要检查的小队位置，如果有位置等于现有的爬的位置则不用检查
            const nextCheckPos = nextTeamPos.filter((pos) => !creeps.some((creep) => creep.pos.isEqualTo(pos)))
            // 需要考虑其他爬
            return nextCheckPos.every((pos) => pos.walkable(true))
        }

        const diffX = targetPos.x - originPos.x
        const diffY = targetPos.y - originPos.y
        if (diffX == -1 && diffY == -1) {
            // 左上角
            if (checkTeamMoveAble(LEFT)) return LEFT
            if (checkTeamMoveAble(TOP)) return TOP
        } else if (diffX == 2 && diffY == -1) {
            // 右上角
            if (checkTeamMoveAble(TOP)) return TOP
            if (checkTeamMoveAble(RIGHT)) return RIGHT
        } else if (diffX == 2 && diffY == 2) {
            // 右下角
            if (checkTeamMoveAble(RIGHT)) return RIGHT
            if (checkTeamMoveAble(BOTTOM)) return BOTTOM
        } else if (diffX == -1 && diffY == 2) {
            // 左下角
            if (checkTeamMoveAble(BOTTOM)) return BOTTOM
            if (checkTeamMoveAble(LEFT)) return LEFT
        }

        return undefined
    }

    /**
     * 获取小队左上角坐标
     */
    public static getOriginPos(flag: Flag) {
        const creeps = flag._creeps
        if (creeps.length <= 2) {
            // 二人小队逃跑时倒着走
            if (flag.memory.status === 'flee' || flag.memory.status === 'avoid') {
                return creeps[creeps.length - 1].pos
            }
            return creeps[0].pos
        }

        // 所有爬中 x+y 坐标最小的就是左上角队员的位置
        let sum = Infinity
        let originPos = null
        for (const { pos } of creeps) {
            const { x, y } = pos.toGlobal()
            if (sum > x + y) {
                sum = x + y
                originPos = pos
            }
        }
        return originPos
    }

    /**
     * 是否是四人队形
     *
     * @returns true 代表队形完整，false 代表队形不完整，undefined 代表跨房了
     */
    public static isSquare(creeps: Creep[]) {
        // 跨房了不检查
        // if (new Set(creeps.map((creep) => creep.room.name)).size > 1) return

        // 检测每个爬是否与其余所有爬相邻
        for (let i = 0; i < creeps.length; i++) {
            const creep = creeps[i]
            for (let j = 0; j < creeps.length; j++) {
                if (i === j) continue
                if (!creep.pos.isCrossRoomNearTo(creeps[j].pos)) return false
            }
        }

        return true
    }

    /**
     * 计算 tick 步内能否走到目标指定范围内
     * 用于 tick 较小的场景
     */
    public static touchableNTickInRange(
        creep: Creep,
        targetPos: RoomPosition,
        tick: number,
        range: number,
        plainCost = 1,
    ) {
        if (tick > 100) throw new Error(`tick 数量过大，可能会导致性能问题`)
        if (tick === 0) return creep.pos.inRangeTo(targetPos, range)

        const username = creep.owner.username
        const goal = { pos: targetPos, range }
        const result = PathFinder.search(creep.pos, [goal], {
            maxCost: tick,
            plainCost,
            swampCost: plainCost * 5,
            // @ts-ignore
            roomCallback: (roomName) => {
                const room = Game.rooms[roomName]
                if (!room) return emptyCostMatrix

                if (!Game._username_costs) Game._username_costs = {}
                const id = roomName + username

                if (!Game._username_costs[id]) {
                    const costs = new PathFinder.CostMatrix()

                    const structures = StructureCache.getStructures(room)
                    // 不考虑 constructedWall 上造路的情况
                    structures.forEach((struct) => {
                        if (struct.structureType === STRUCTURE_ROAD) {
                            costs.set(struct.pos.x, struct.pos.y, 1)
                        } else if (
                            struct.structureType !== STRUCTURE_CONTAINER &&
                            (struct.structureType !== STRUCTURE_RAMPART || !struct.my)
                        ) {
                            costs.set(struct.pos.x, struct.pos.y, 255)
                        }
                    })

                    Game._username_costs[id] = costs
                }

                return Game._username_costs[id]
            },
        })

        return !result.incomplete
    }

    /**
     * 获取 CostMatrix
     *
     * @param roomName 房间名
     * @param myCreeps 我的爬
     * @param avoidObjects 需要避开的对象
     * @param damageLimit 伤害阈值
     * @param damageRangePlus 伤害范围增加
     * @param isFourTeam 是否是四人小队
     * @param swampCost 沼泽消耗
     * @param structHitLimit 建筑血量阈值，低于该值的建筑不会被视为障碍物
     * @param isSpawnDanger spawn 是否危险，即 spawn 周围 1 格是否禁止通行
     */
    public static getMoveAbleCostMatrix(
        roomName: string,
        myCreeps: Creep[],
        avoidObjects?: { pos: RoomPosition; range: number }[],
        damageLimit = 0,
        damageRangePlus = 0,
        isFourTeam = false,
        swampCost = 5,
        structHitLimit = BattleMove.structHitLimit,
        isSpawnDanger = false,
    ) {
        const costs = new PathFinder.CostMatrix()
        const terrain = new Room.Terrain(roomName)
        const room = Game.rooms[roomName]
        const towerDamageMap =
            damageLimit > 0 && (!room || !isMyRoom(room))
                ? BattleCache.getTowerDamageMap(roomName)
                : BattleCache.emptyRoomArray

        // 设置地形数据，同时避开塔伤会导致破防的地方
        towerDamageMap.forEach((x, y, damage) => {
            const terrainType = terrain.get(x, y)
            costs.set(
                x,
                y,
                terrainType === TERRAIN_MASK_WALL ? 255 : terrainType === TERRAIN_MASK_SWAMP ? swampCost : 1,
            )
            // 超过伤害阈值的地方设置为无法通过
            if (damage > damageLimit && terrainType !== TERRAIN_MASK_WALL) {
                costs.set(x, y, 254)
            }
        })

        const structures = BattleCache.getStructures(roomName)

        structures.forEach((struct) => {
            if (struct.structureType === STRUCTURE_ROAD) {
                costs.set(struct.pos.x, struct.pos.y, 1)
            }
        })

        // 不能行走的建筑
        structures.forEach((struct) => {
            if (
                struct.structureType !== STRUCTURE_ROAD &&
                struct.structureType !== STRUCTURE_CONTAINER &&
                (struct.structureType !== STRUCTURE_RAMPART || !struct.my)
            ) {
                if (!struct.hits || struct.hits >= structHitLimit) {
                    costs.set(struct.pos.x, struct.pos.y, 255)
                } else {
                    costs.set(struct.pos.x, struct.pos.y, 100)
                }
            }

            if (isSpawnDanger && struct.structureType === STRUCTURE_SPAWN) {
                struct.pos.nearPos().forEach((pos) => {
                    costs.set(pos.x, pos.y, 255)
                })
            }
        })

        const myCreepsIdSet = new Set(myCreeps.map((creep) => creep.id))
        let init = false

        if (room) {
            // 房间存在的话，避开房间中的非小队 creep，暂时不考虑 pc（不会真有 pc 直接撞上来吧？）
            room.find(FIND_CREEPS).forEach((creep) => {
                if (!myCreepsIdSet.has(creep.id)) {
                    costs.set(creep.pos.x, creep.pos.y, 255)
                }

                if (creep.my) return

                // 敌人单位要计算周围伤害
                const atkDamage = BattleCalc.calcAttackDamage(creep)
                const rangeDamage = BattleCalc.calcRangeDamage(creep)

                if (atkDamage + rangeDamage > 0 && !init) {
                    init = true
                    tempRoomArray.fill(0)
                }

                if (atkDamage > 0) {
                    tempRoomArray.forNear(creep.pos.x, creep.pos.y, 2 + damageRangePlus, (x, y, val) => {
                        tempRoomArray.set(x, y, val + atkDamage)
                    })
                }

                if (rangeDamage > 0) {
                    tempRoomArray.forNear(creep.pos.x, creep.pos.y, 4 + damageRangePlus, (x, y, val) => {
                        tempRoomArray.set(x, y, val + rangeDamage)
                    })
                    tempRoomArray.forNear(creep.pos.x, creep.pos.y, 2 + damageRangePlus, (x, y, val) => {
                        // 对四人小队的多余伤害：1 + 0.4 + 0.4 = 1.8
                        tempRoomArray.set(x, y, val + rangeDamage * 1.8)
                    })
                }
            })
        }

        if (init) {
            tempRoomArray.forEach((x, y, val) => {
                const damage = towerDamageMap.get(x, y) + val
                // 超过伤害阈值的地方设置为几乎无法通过
                if (damage > damageLimit && costs.get(x, y) < 249) costs.set(x, y, 249)
            })
        }

        if (avoidObjects?.length) {
            avoidObjects.forEach((e) => {
                if (e.pos.roomName !== roomName) return

                tempRoomArray.forNear(e.pos.x, e.pos.y, e.range, (x, y, _) => {
                    if (costs.get(x, y) < 249) costs.set(x, y, 249)
                })
            })
        }

        let val: number
        for (let x = 0; x < 49; x++) {
            for (let y = 0; y < 49; y++) {
                val = costs.get(x, y)
                if (val < 250 && terrain.get(x, y) === TERRAIN_MASK_SWAMP) {
                    costs.set(x, y, val + 5)
                }
            }
        }

        if (isFourTeam) {
            // 如果是四人小队就调整一下
            for (let x = 0; x < 49; x++) {
                for (let y = 0; y < 49; y++) {
                    // 尽量不出房间
                    if (x == 0 || y == 0 || y == 48 || x == 48) {
                        costs.set(x, y, 50)
                    }

                    const c = Math.max(
                        costs.get(x, y),
                        costs.get(x + 1, y),
                        costs.get(x, y + 1),
                        costs.get(x + 1, y + 1),
                    )
                    costs.set(x, y, c)
                }
            }
        }

        return costs
    }

    /**
     * 是否有东西挡在路径上
     */
    public static hasObstacleInPath(creeps: Creep[], nextPos: RoomPosition) {
        if (!nextPos) return true

        const directions = [RIGHT, BOTTOM_RIGHT, BOTTOM]
        const isFourTeam = creeps.length >= 3
        const posList = isFourTeam ? directions.map((dir) => nextPos.getDirectPos(dir)) : []
        posList.push(nextPos)

        const creepsPosSet = new Set(creeps.map((creep) => creep.pos.hashCode()))

        return !!posList.find((pos) => !creepsPosSet.has(pos.hashCode()) && !pos.walkable(true))
    }

    /**
     * 锁定目标位置
     */
    public static focusTarget(flag: Flag, originPos: RoomPosition) {
        let targets: (Creep | Structure)[] = []
        if (flag._attackTargets?.length) {
            targets = flag._attackTargets
        } else if (flag._targets?.length) {
            targets = flag._targets.filter((s) => 'hits' in s) as (Creep | Structure)[]
        }
        return originPos.findClosestByRange(targets)
    }

    /**
     * 四人小队变换队形，使得攻击力最大的两个爬朝向目标
     *
     * @returns 是否变换过队形
     */
    public static switchTeam4Pos(flag: Flag) {
        if (flag.memory.status === 'flee') return false

        const creeps = flag._creeps
        if (creeps.length <= 2) return false

        const originPos = BattleMove.getOriginPos(flag)!
        const targetPos = BattleMove.focusTarget(flag, originPos)?.pos || flag.pos

        if (!targetPos) return false

        // 目标位置相对于左上角的坐标
        const { x, y } = targetPos.crossRoomSubPos(originPos)
        // 目标位置相对于四人小队中心的坐标
        const dx = x - 0.5
        const dy = y - 0.5
        // 绝对值
        const adx = Math.abs(dx)
        const ady = Math.abs(dy)

        // 过远不管
        if (adx >= 5 || ady >= 5) return false

        // 在对角线上就不管
        if (adx === ady) {
            return false
        }

        // 小队的四个位置
        const teamPosList = [
            [originPos, originPos.getDirectPos(BOTTOM)],
            [originPos.getDirectPos(RIGHT), originPos.getDirectPos(BOTTOM_RIGHT)],
        ]

        // 是否交换了位置
        let switched = false
        // 攻击爬，为什么直接取前两个，因为孵化的时候已经按照伤害从大到小排序了
        const attackCreeps = creeps.slice(0, 2)
        // 位置到爬的映射
        const posCreepMap = creeps.reduce((map, creep) => {
            map.set(creep.pos.hashCodeInRoom(), creep)
            return map
        }, new Map<number, Creep>())

        const switchPos = (attackPosList: RoomPosition[]) => {
            attackCreeps.forEach((creep) => {
                // 已经在攻击位置了
                if (attackPosList.some((pos) => pos.isEqualTo(creep.pos))) return

                for (const pos of attackPosList) {
                    const otherCreep = posCreepMap.get(pos.hashCodeInRoom())
                    // 另一个爬是攻击爬或者移动过
                    if (otherCreep && (attackCreeps.includes(otherCreep) || otherCreep._moved)) {
                        continue
                    }

                    // 交换位置
                    creep.move(creep.pos.getDirectionTo(pos))
                    switched = true

                    if (otherCreep) {
                        otherCreep.move(pos.getDirectionTo(creep.pos))
                        otherCreep._moved = true
                    }

                    return
                }
            })
        }

        // 小队在右边，攻击爬调到左边
        if (dx < 0 && adx > ady) {
            switchPos([teamPosList[0][0], teamPosList[0][1]])
        }
        // 小队在左边，攻击爬调到右边
        else if (dx > 0 && adx > ady) {
            switchPos([teamPosList[1][0], teamPosList[1][1]])
        }
        // 小队在下边，攻击爬调到上边
        else if (dy < 0 && adx < ady) {
            switchPos([teamPosList[0][0], teamPosList[1][0]])
        }
        // 小队在上边，攻击爬调到下边
        else if (dy > 0 && adx < ady) {
            switchPos([teamPosList[0][1], teamPosList[1][1]])
        }

        return switched
    }

    /**
     * 移出边界
     */
    public static moveOuterBorder(creep: Creep, nearCreep: Creep) {
        const targetPos = creep.pos.nearPos(1).find((pos) => pos.walkable(true) && nearCreep.pos.isNearTo(pos))

        if (targetPos) {
            creep.move(creep.pos.getDirectionTo(targetPos))
        }
    }

    /**
     * 小队中是否有爬在房间边缘
     */
    public static hasCreepOnEdge(creeps: Creep[]) {
        return creeps.some((creep) => onEdge(creep.pos))
    }

    /**
     * 小队中是否有爬疲劳
     */
    public static hasCreepFatigue(creeps: Creep[]) {
        return creeps.some((creep) => creep.fatigue > 0)
    }

    /**
     * 删除寻路缓存
     */
    public static clearPathCache(flag: Flag) {
        delete BattleMove.cacheTeamPath[flag.name]
    }
}

defineGlobalProperty('BattleMove', {
    value: BattleMove,
})
